/* Support for embedding graphical components in a buffer.

Copyright (C) 2011-2015 Free Software Foundation, Inc.

This file is part of GNU Emacs.

GNU Emacs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */

#include <config.h>


#include <signal.h>

#include <stdio.h>
#include <setjmp.h>
#ifdef HAVE_X_WINDOWS

#include "lisp.h"
#include "blockinput.h"
#include "syssignal.h"

#include "xterm.h"
#include <X11/cursorfont.h>

#ifndef makedev
# include <sys/types.h>
#endif

#ifdef BSD_SYSTEM
# include <sys/ioctl.h>
#endif

#include "systime.h"

#ifndef INCLUDED_FCNTL
# include <fcntl.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>
#include <sys/stat.h>

#include "charset.h"
#include "character.h"
#include "coding.h"
#include "ccl.h"
#include "frame.h"
#include "dispextern.h"
#include "fontset.h"
#include "termhooks.h"
#include "termopts.h"
#include "termchar.h"
#include "disptab.h"
#include "buffer.h"
#include "window.h"
#include "keyboard.h"
#include "intervals.h"
#include "process.h"
#include "atimer.h"
#include "keymap.h"


#ifdef USE_X_TOOLKIT
#include <X11/Shell.h>
#endif
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xrender.h>
#include <cairo.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "gtkutil.h"
#include "font.h"
#endif /* HAVE_X_WINDOWS */

#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include <gtk/gtkx.h>

#include "emacsgtkfixed.h"

#include <wchar.h>

#include <webkit/webkitwebview.h>
#include <webkit/webkitwebplugindatabase.h>
#include <webkit/webkitwebplugin.h>
#include <webkit/webkitglobals.h>
#include <webkit/webkitwebnavigationaction.h>
#include <webkit/webkitdownload.h>
#include <webkit/webkitwebpolicydecision.h>

#include "xwidget.h"

static struct xwidget *
allocate_xwidget (void)
{
  return ALLOCATE_PSEUDOVECTOR (struct xwidget, height, PVEC_XWIDGET);
}

static struct xwidget_view *
allocate_xwidget_view (void)
{
  return ALLOCATE_PSEUDOVECTOR (struct xwidget_view, redisplayed,
                                PVEC_XWIDGET_VIEW);
}

#define XSETXWIDGET(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET))
#define XSETXWIDGET_VIEW(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_XWIDGET_VIEW))

struct xwidget_view *xwidget_view_lookup (struct xwidget *, struct window *);
Lisp_Object xwidget_spec_value (Lisp_Object , Lisp_Object , int *);
gboolean offscreen_damage_event (GtkWidget * , GdkEvent * , gpointer );
void webkit_document_load_finished_cb (WebKitWebView *, WebKitWebFrame *,
                                       gpointer );
gboolean webkit_download_cb (WebKitWebView *, WebKitDownload *, gpointer);

gboolean
webkit_mime_type_policy_typedecision_requested_cb (WebKitWebView *,
                                                   WebKitWebFrame *,
                                                   WebKitNetworkRequest *,
                                                   gchar *,
                                                   WebKitWebPolicyDecision *,
                                                   gpointer);

gboolean
webkit_new_window_policy_decision_requested_cb (WebKitWebView *,
                                                WebKitWebFrame *,
                                                WebKitNetworkRequest *,
                                                WebKitWebNavigationAction *,
                                                WebKitWebPolicyDecision *,
                                                gpointer);

gboolean
webkit_navigation_policy_decision_requested_cb (WebKitWebView *,
                                                WebKitWebFrame *,
                                                WebKitNetworkRequest *,
                                                WebKitWebNavigationAction *,
                                                WebKitWebPolicyDecision *,
                                                gpointer);



DEFUN ("make-xwidget",
       Fmake_xwidget, Smake_xwidget,
       7, 8, 0,
       doc: /* Make an xwidget from BEG to END of TYPE.

If BUFFER is nil it uses the current
buffer. If BUFFER is a string and no such
buffer exists, it is created.

TYPE is a symbol which can take one of the
following values:

- webkit_osr

Returns the newly constructed xwidget, or nil if construction
fails.  */)
  (Lisp_Object beg, Lisp_Object end,
  Lisp_Object type,
  Lisp_Object title,
  Lisp_Object width, Lisp_Object height,
  Lisp_Object arguments, Lisp_Object buffer)
{
  //should work a bit like "make-button"(make-button BEG END &rest PROPERTIES)
  // arg "type" and fwd should be keyword args eventually
  //(make-xwidget 3 3 'button "oei" 31 31 nil)
  //(xwidget-info (car xwidget-list))
  struct xwidget *xw = allocate_xwidget ();
  Lisp_Object val;
  xw->type = type;
  xw->title = title;
  if (NILP (buffer))
    buffer = Fcurrent_buffer ();	// no need to gcpro because
                                        // Fcurrent_buffer doesn't
                                        // call Feval/eval_sub.
  else
    buffer = Fget_buffer_create (buffer);
  xw->buffer = buffer;

  xw->height = XFASTINT (height);
  xw->width = XFASTINT (width);
  xw->kill_without_query = 0;
  XSETXWIDGET (val, xw);	// set the vectorlike_header of VAL
                                // with the correct value
  Vxwidget_list = Fcons (val, Vxwidget_list);
  xw->widgetwindow_osr = NULL;
  xw->widget_osr = NULL;
  xw->plist = Qnil;


  if (EQ (xw->type, Qwebkit_osr))
    {
      block_input ();
      xw->widgetwindow_osr = gtk_offscreen_window_new ();
      gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
                         xw->height);
      xw->widgetscrolledwindow_osr = NULL;	//webkit osr is the
                                                //only scrolled
                                                //component atm

      if (EQ (xw->type, Qwebkit_osr))
        {
          xw->widgetscrolledwindow_osr = gtk_scrolled_window_new (NULL, NULL);
          gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW
                                                      (xw->
                                                       widgetscrolledwindow_osr),
                                                      xw->height);
          gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW
                                                     (xw->
                                                      widgetscrolledwindow_osr),
                                                     xw->width);
          gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW
                                          (xw->widgetscrolledwindow_osr),
                                          GTK_POLICY_ALWAYS,
                                          GTK_POLICY_ALWAYS);

          xw->widget_osr = webkit_web_view_new ();
          gtk_container_add (GTK_CONTAINER (xw->widgetscrolledwindow_osr),
                             GTK_WIDGET (WEBKIT_WEB_VIEW (xw->widget_osr)));
        }

      gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr), xw->width,
                                   xw->height);

      if (EQ (xw->type, Qwebkit_osr))
        {
          gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr),
                             xw->widgetscrolledwindow_osr);
        }
      else
        {
          gtk_container_add (GTK_CONTAINER (xw->widgetwindow_osr),
                             xw->widget_osr);
        }

      gtk_widget_show (xw->widget_osr);
      gtk_widget_show (xw->widgetwindow_osr);
      gtk_widget_show (xw->widgetscrolledwindow_osr);

      /* store some xwidget data in the gtk widgets for convenient
         retrieval in the event handlers. */
      g_object_set_data (G_OBJECT (xw->widget_osr), XG_XWIDGET,
                         (gpointer) (xw));
      g_object_set_data (G_OBJECT (xw->widgetwindow_osr), XG_XWIDGET,
                         (gpointer) (xw));

      /* signals */
      if (EQ (xw->type, Qwebkit_osr))
        {
          g_signal_connect (G_OBJECT (xw->widget_osr),
                            "document-load-finished",
                            G_CALLBACK
                            (webkit_document_load_finished_cb), xw);

          g_signal_connect (G_OBJECT (xw->widget_osr),
                            "download-requested",
                            G_CALLBACK (webkit_download_cb), xw);

          g_signal_connect (G_OBJECT (xw->widget_osr),
                            "mime-type-policy-decision-requested",
                            G_CALLBACK
                            (webkit_mime_type_policy_typedecision_requested_cb),
                            xw);

          g_signal_connect (G_OBJECT (xw->widget_osr),
                            "new-window-policy-decision-requested",
                            G_CALLBACK
                            (webkit_new_window_policy_decision_requested_cb),
                            xw);

          g_signal_connect (G_OBJECT (xw->widget_osr),
                            "navigation-policy-decision-requested",
                            G_CALLBACK
                            (webkit_navigation_policy_decision_requested_cb),
                            xw);
        }

      unblock_input ();

    }

  return val;
}

DEFUN ("get-buffer-xwidgets", Fget_buffer_xwidgets, Sget_buffer_xwidgets,
       1, 1, 0,
       doc: /* Return a list of xwidgets associated with BUFFER.
BUFFER  may be a buffer or the name of one.  */)
  (Lisp_Object buffer)
{
  Lisp_Object xw, tail, xw_list;

  if (NILP (buffer))
    return Qnil;
  buffer = Fget_buffer (buffer);
  if (NILP (buffer))
    return Qnil;

  xw_list = Qnil;

  for (tail = Vxwidget_list; CONSP (tail); tail = XCDR (tail))
    {
      xw = XCAR (tail);
      if (XWIDGETP (xw) && EQ (Fxwidget_buffer (xw), buffer))
        xw_list = Fcons (xw, xw_list);
    }
  return xw_list;
}

static int
xwidget_hidden (struct xwidget_view *xv)
{
  return xv->hidden;
}



static void
xwidget_show_view (struct xwidget_view *xv)
{
  xv->hidden = 0;
  gtk_widget_show (xv->widgetwindow);
  gtk_fixed_move (GTK_FIXED (xv->emacswindow),
                  xv->widgetwindow,
                  xv->x + xv->clip_left,
                  xv->y + xv->clip_top);
}


/* Hide an xvidget view.  */
static void
xwidget_hide_view (struct xwidget_view *xv)
{
  xv->hidden = 1;
  gtk_fixed_move (GTK_FIXED (xv->emacswindow), xv->widgetwindow,
                  10000, 10000);
}



/* When the off-screen webkit master view changes this signal is called.
   It copies the bitmap from the off-screen instance.  */
gboolean
offscreen_damage_event (GtkWidget * widget, GdkEvent * event,
                        gpointer xv_widget)
{
  // Queue a redraw of onscreen widget.
  // There is a guard against receiving an invalid widget,
  // which should only happen if we failed to remove the
  // specific signal handler for the damage event.
  if (GTK_IS_WIDGET (xv_widget))
    gtk_widget_queue_draw (GTK_WIDGET (xv_widget));
  else
    printf ("Warning, offscreen_damage_event received invalid xv pointer:%p\n",
            (void *) xv_widget);

  return FALSE;
}

static void
store_xwidget_event_string (struct xwidget *xw, const char *eventname,
                            const char *eventstr)
{
  struct input_event event;
  Lisp_Object xwl;
  XSETXWIDGET (xwl, xw);
  EVENT_INIT (event);
  event.kind = XWIDGET_EVENT;
  event.frame_or_window = Qnil;

  event.arg = Qnil;
  event.arg = Fcons (build_string (eventstr), event.arg);
  event.arg = Fcons (xwl, event.arg);
  event.arg = Fcons (intern (eventname), event.arg);
  kbd_buffer_store_event (&event);

}

//TODO deprecated, use load-status
void
webkit_document_load_finished_cb (WebKitWebView * webkitwebview,
                                  WebKitWebFrame * arg1,
                                  gpointer data)
{
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (webkitwebview),
                                          XG_XWIDGET);

  store_xwidget_event_string (xw, "document-load-finished", "");
}

gboolean
webkit_download_cb (WebKitWebView * webkitwebview,
                    WebKitDownload * arg1,
                    gpointer data)
{
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (webkitwebview),
                                          XG_XWIDGET);
  store_xwidget_event_string (xw, "download-requested",
                              webkit_download_get_uri (arg1));

  return FALSE;
}

gboolean
webkit_mime_type_policy_typedecision_requested_cb (WebKitWebView *webView,
                                                   WebKitWebFrame *frame,
                                                   WebKitNetworkRequest * request,
                                                   gchar * mimetype,
                                                   WebKitWebPolicyDecision *policy_decision,
                                                   gpointer user_data)
{
  // This function makes webkit send a download signal for all unknown
  // mime types. TODO Defer the decision to lisp, so that its possible
  // to make Emacs handle teext mime for instance.xs
  if (!webkit_web_view_can_show_mime_type (webView, mimetype))
    {
      webkit_web_policy_decision_download (policy_decision);
      return TRUE;
    }
  else
    {
      return FALSE;
    }
}


gboolean
webkit_new_window_policy_decision_requested_cb (WebKitWebView *webView,
                                                WebKitWebFrame *frame,
                                                WebKitNetworkRequest *request,
                                                WebKitWebNavigationAction *navigation_action,
                                                WebKitWebPolicyDecision *policy_decision,
                                                gpointer user_data)
{
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (webView), XG_XWIDGET);
  webkit_web_navigation_action_get_original_uri (navigation_action);

  store_xwidget_event_string (xw, "new-window-policy-decision-requested",
                              webkit_web_navigation_action_get_original_uri
                              (navigation_action));
  return FALSE;
}

gboolean
webkit_navigation_policy_decision_requested_cb (WebKitWebView *webView,
                                                WebKitWebFrame *frame,
                                                WebKitNetworkRequest *request,
                                                WebKitWebNavigationAction *navigation_action,
                                                WebKitWebPolicyDecision * policy_decision,
                                                gpointer user_data)
{
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (webView), XG_XWIDGET);
  store_xwidget_event_string (xw, "navigation-policy-decision-requested",
                              webkit_web_navigation_action_get_original_uri
                              (navigation_action));
  return FALSE;
}

// For gtk3 offscreen rendered widgets.
static gboolean
xwidget_osr_draw_cb (GtkWidget * widget, cairo_t * cr, gpointer data)
{
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
  struct xwidget_view *xv =
    (struct xwidget_view *) g_object_get_data (G_OBJECT (widget),
                                               XG_XWIDGET_VIEW);

  cairo_rectangle (cr, 0, 0, xv->clip_right, xv->clip_bottom);
  cairo_clip (cr);

  if (xw->widgetscrolledwindow_osr != NULL)
    gtk_widget_draw (xw->widgetscrolledwindow_osr, cr);
  else
    gtk_widget_draw (xw->widget_osr, cr);
  return FALSE;
}

static gboolean
xwidget_osr_event_forward (GtkWidget * widget,
                           GdkEvent * event,
                           gpointer user_data)
{
  /* Copy events that arrive at the outer widget to the offscreen widget. */
  struct xwidget *xw =
    (struct xwidget *) g_object_get_data (G_OBJECT (widget), XG_XWIDGET);
  GdkEvent *eventcopy = gdk_event_copy (event);
  eventcopy->any.window = gtk_widget_get_window (xw->widget_osr);

  //TODO This might leak events. They should be deallocated later,
  //perhaps in xwgir_event_cb
  gtk_main_do_event (eventcopy);
  return TRUE;			//dont propagate this event furter
}


static gboolean
xwidget_osr_event_set_embedder (GtkWidget * widget,
                                GdkEvent * event, gpointer data)
{
  struct xwidget_view *xv = (struct xwidget_view *) data;
  struct xwidget *xww = XXWIDGET (xv->model);
  gdk_offscreen_window_set_embedder (gtk_widget_get_window
                                     (xww->widgetwindow_osr),
                                     gtk_widget_get_window (xv->widget));
  return FALSE;
}


/* Initializes and does initial placement of an xwidget view on screen. */
static struct xwidget_view *
xwidget_init_view (struct xwidget *xww,
                   struct glyph_string *s,
                   int x, int y)
{
  struct xwidget_view *xv = allocate_xwidget_view ();
  Lisp_Object val;

  XSETXWIDGET_VIEW (val, xv);
  Vxwidget_view_list = Fcons (val, Vxwidget_view_list);

  XSETWINDOW (xv->w, s->w);
  XSETXWIDGET (xv->model, xww);

  if (EQ (xww->type, Qwebkit_osr))
    {
      xv->widget = gtk_drawing_area_new ();
      // Expose event handling.
      gtk_widget_set_app_paintable (xv->widget, TRUE);
      gtk_widget_add_events (xv->widget, GDK_ALL_EVENTS_MASK);

      /* Draw the view on damage-event */
      g_signal_connect (G_OBJECT (xww->widgetwindow_osr), "damage-event",
                        G_CALLBACK (offscreen_damage_event), xv->widget);

      if (EQ (xww->type, Qwebkit_osr))
        {
          g_signal_connect (G_OBJECT (xv->widget), "button-press-event",
                            G_CALLBACK (xwidget_osr_event_forward), NULL);
          g_signal_connect (G_OBJECT (xv->widget), "button-release-event",
                            G_CALLBACK (xwidget_osr_event_forward), NULL);
          g_signal_connect (G_OBJECT (xv->widget), "motion-notify-event",
                            G_CALLBACK (xwidget_osr_event_forward), NULL);
        }
      else
        {
          // xwgir debug , orthogonal to forwarding
          g_signal_connect (G_OBJECT (xv->widget), "enter-notify-event",
                            G_CALLBACK (xwidget_osr_event_set_embedder), xv);
        }
      g_signal_connect (G_OBJECT (xv->widget), "draw",
                        G_CALLBACK (xwidget_osr_draw_cb), NULL);
    }
  // Widget realization.

  // Make container widget 1st, and put the actual widget inside the
  // container later.  Drawing should crop container window if necessary
  // to handle case where xwidget is partially obscured by other Emacs
  // windows.  Other containers than gtk_fixed where explored, but
  // gtk_fixed had the most predictable behaviour so far.
  xv->emacswindow = FRAME_GTK_WIDGET (s->f);
  xv->widgetwindow = gtk_fixed_new ();
  gtk_widget_set_has_window (xv->widgetwindow, TRUE);
  gtk_container_add (GTK_CONTAINER (xv->widgetwindow), xv->widget);

  // Store some xwidget data in the gtk widgets.
  // The emacs frame.
  g_object_set_data (G_OBJECT (xv->widget), XG_FRAME_DATA, (gpointer) (s->f));
  // The xwidget.
  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET, (gpointer) (xww));
  // The xwidget.
  g_object_set_data (G_OBJECT (xv->widget), XG_XWIDGET_VIEW, (gpointer) (xv));
  // The xwidget window.
  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET, (gpointer) (xww));
  // the xwidget view.
  g_object_set_data (G_OBJECT (xv->widgetwindow), XG_XWIDGET_VIEW,
                     (gpointer) (xv));


  gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xww->width,
                               xww->height);
  gtk_widget_set_size_request (xv->widgetwindow, xww->width, xww->height);
  gtk_fixed_put (GTK_FIXED (FRAME_GTK_WIDGET (s->f)), xv->widgetwindow, x, y);
  xv->x = x;
  xv->y = y;
  gtk_widget_show_all (xv->widgetwindow);


  return xv;
}


void
x_draw_xwidget_glyph_string (struct glyph_string *s)
{
  /* This method is called by the redisplay engine and places the
     xwidget on screen.  Moving and clipping is done here.  Also view
     initialization.
   */
  struct xwidget *xww = s->xwidget;
  struct xwidget_view *xv = xwidget_view_lookup (xww, s->w);
  int clip_right;
  int clip_bottom;
  int clip_top;
  int clip_left;

  int x = s->x;
  int y = s->y + (s->height / 2) - (xww->height / 2);
  int moved = 0;

  /* We do initialization here in the display loop because there is no
     other time to know things like window placement etc.
   */
  xv = xwidget_init_view (xww, s, x, y);

  // Calculate clipping, which is used for all manner of onscreen
  // xwidget views.  Each widget border can get clipped by other emacs
  // objects so there are four clipping variables.
  clip_right =
    min (xww->width,
         WINDOW_RIGHT_EDGE_X (s->w) - x -
         WINDOW_RIGHT_SCROLL_BAR_AREA_WIDTH (s->w) -
         WINDOW_RIGHT_FRINGE_WIDTH (s->w));
  clip_left =
    max (0,
         WINDOW_LEFT_EDGE_X (s->w) - x +
         WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w) +
         WINDOW_LEFT_FRINGE_WIDTH (s->w));

  clip_bottom =
    min (xww->height,
         WINDOW_BOTTOM_EDGE_Y (s->w) - WINDOW_MODE_LINE_HEIGHT (s->w) - y);
  clip_top = max (0, WINDOW_TOP_EDGE_Y (s->w) - y);

  // We are conserned with movement of the onscreen area. The area
  // might sit still when the widget actually moves.  This happens
  // when an Emacs window border moves across a widget window. So, if
  // any corner of the outer widget clipping window moves, that counts
  // as movement here, even if it looks like no movement happens
  // because the widget sits still inside the clipping area.  The
  // widget can also move inside the clipping area, which happens
  // later
  moved = (xv->x + xv->clip_left != x + clip_left)
    || ((xv->y + xv->clip_top) != (y + clip_top));
  xv->x = x;
  xv->y = y;
  if (moved) // Has it moved?
    {
          gtk_fixed_move (GTK_FIXED (FRAME_GTK_WIDGET (s->f)),
                          xv->widgetwindow, x + clip_left, y + clip_top);
    }
  // Clip the widget window if some parts happen to be outside
  // drawable area. An Emacs window is not a gtk window. A gtk window
  // covers the entire frame. Clipping might have changed even if we
  // havent actualy moved, we try figure out when we need to reclip
  // for real.
  if ((xv->clip_right != clip_right)
      || (xv->clip_bottom != clip_bottom)
      || (xv->clip_top != clip_top) || (xv->clip_left != clip_left))
    {
      gtk_widget_set_size_request (xv->widgetwindow, clip_right + clip_left,
                                   clip_bottom + clip_top);
      gtk_fixed_move (GTK_FIXED (xv->widgetwindow), xv->widget, -clip_left,
                      -clip_top);

      xv->clip_right = clip_right;
      xv->clip_bottom = clip_bottom;
      xv->clip_top = clip_top;
      xv->clip_left = clip_left;
    }
  // If emacs wants to repaint the area where the widget lives, queue
  // a redraw. It seems its possible to get out of sync with emacs
  // redraws so emacs background sometimes shows up instead of the
  // xwidgets background. It's just a visual glitch though.
  if (!xwidget_hidden (xv))
    {
      gtk_widget_queue_draw (xv->widgetwindow);
      gtk_widget_queue_draw (xv->widget);
    }
}


// Macro that checks WEBKIT_IS_WEB_VIEW(xw->widget_osr) first
#define WEBKIT_FN_INIT()                        \
  struct xwidget* xw; \
  CHECK_XWIDGET (xwidget); \
 if (NILP (xwidget)) {printf("ERROR xwidget nil\n"); return Qnil;};    \
  xw = XXWIDGET (xwidget);                                                    \
  if (NULL == xw) printf("ERROR xw is 0\n");                               \
  if ((NULL == xw->widget_osr) || !WEBKIT_IS_WEB_VIEW(xw->widget_osr)){  \
    printf ("ERROR xw->widget_osr does not hold a webkit instance\n");\
    return Qnil;\
  };


DEFUN ("xwidget-webkit-goto-uri",
       Fxwidget_webkit_goto_uri, Sxwidget_webkit_goto_uri,
       2, 2, 0,
       doc: /* Make the xwidget webkit instance referenced by XWIDGET
browse URI. */)
  (Lisp_Object xwidget, Lisp_Object uri)
{
  WEBKIT_FN_INIT ();
  CHECK_STRING (uri);
  webkit_web_view_load_uri (WEBKIT_WEB_VIEW (xw->widget_osr), SSDATA (uri));
  return Qnil;
}


DEFUN ("xwidget-webkit-execute-script",
       Fxwidget_webkit_execute_script, Sxwidget_webkit_execute_script,
       2, 2, 0,
       doc: /* Make the Webkit XWIDGET execute javascript SCRIPT.  */)
  (Lisp_Object xwidget, Lisp_Object script)
{
  WEBKIT_FN_INIT ();
  CHECK_STRING (script);
  webkit_web_view_execute_script (WEBKIT_WEB_VIEW (xw->widget_osr),
                                  SSDATA (script));
  return Qnil;
}

DEFUN ("xwidget-webkit-get-title",
       Fxwidget_webkit_get_title, Sxwidget_webkit_get_title,
       1, 1, 0,
       doc: /* Returns the title from the Webkit instance in XWIDGET.
This can be used to work around the lack of a return value from the
exec method.  */ )
  (Lisp_Object xwidget)
{
  // TODO support multibyte strings
  WEBKIT_FN_INIT ();
  const gchar *str =
    webkit_web_view_get_title (WEBKIT_WEB_VIEW (xw->widget_osr));
  if (str == 0)
    {
      // TODO maybe return Qnil instead. I suppose webkit returns
      // nullpointer when doc is not properly loaded or something
      return build_string ("");
    }
  return build_string (str);
}

DEFUN ("xwidget-resize", Fxwidget_resize, Sxwidget_resize, 3, 3, 0,
       doc: /* Resize XWIDGET.  NEW_WIDTH NEW_HEIGHT defines the new
size. */ )
  (Lisp_Object xwidget, Lisp_Object new_width, Lisp_Object new_height)
{
  CHECK_XWIDGET (xwidget);
  struct xwidget *xw = XXWIDGET (xwidget);
  struct xwidget_view *xv;
  int w, h;

  CHECK_NUMBER (new_width);
  CHECK_NUMBER (new_height);
  w = XFASTINT (new_width);
  h = XFASTINT (new_height);

  xw->width = w;
  xw->height = h;
  // If there is a offscreen widget resize it 1st.
  if (xw->widget_osr)
    {
      gtk_widget_set_size_request (GTK_WIDGET (xw->widget_osr),
                                   xw->width, xw->height);	//minimum size
      gtk_window_resize (GTK_WINDOW (xw->widgetwindow_osr), xw->width,
                         xw->height);
      gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW
                                                  (xw->
                                                   widgetscrolledwindow_osr),
                                                  xw->height);
      gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW
                                                 (xw->
                                                  widgetscrolledwindow_osr),
                                                 xw->width);

      gtk_container_resize_children (GTK_CONTAINER (xw->widgetwindow_osr));

    }

  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail); tail = XCDR (tail))
    {
      if (XWIDGET_VIEW_P (XCAR (tail)))
        {
          xv = XXWIDGET_VIEW (XCAR (tail));
          if (XXWIDGET (xv->model) == xw)
              gtk_widget_set_size_request (GTK_WIDGET (xv->widget), xw->width,
                                           xw->height);
        }
    }

  return Qnil;
}



DEFUN ("xwidget-set-adjustment",
       Fxwidget_set_adjustment, Sxwidget_set_adjustment, 4, 4, 0,
       doc: /* Set native scrolling for XWIDGET. AXIS can be 'vertical or
'horizontal. If RELATIVE is t, scroll relative, otherwise absolutely.
VALUE is the amount to scroll, either relatively or absolutely.  */)
  (Lisp_Object xwidget, Lisp_Object axis, Lisp_Object relative,
   Lisp_Object value)
{
  CHECK_XWIDGET (xwidget);
  struct xwidget *xw = XXWIDGET (xwidget);
  GtkAdjustment *adjustment;
  float final_value = 0.0;

  adjustment =
    gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW
                                         (xw->widgetscrolledwindow_osr));
  if (EQ (Qvertical, axis))
    {
      adjustment =
        gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW
                                             (xw->widgetscrolledwindow_osr));
    }
  if (EQ (Qhorizontal, axis))
    {
      adjustment =
        gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW
                                             (xw->widgetscrolledwindow_osr));
    }

  if (EQ (Qt, relative))
    {
      final_value = gtk_adjustment_get_value (adjustment) + XFASTINT (value);
    }
  else
    {
      final_value = 0.0 + XFASTINT (value);
    }

  gtk_adjustment_set_value (adjustment, final_value);

  return Qnil;
}


DEFUN ("xwidget-size-request",
       Fxwidget_size_request, Sxwidget_size_request,
       1, 1, 0,
       doc: /* Return the desired size of the XWIDGET.

This can be used to read the xwidget desired size, and resizes the
Emacs allocated area accordingly.  */)
  (Lisp_Object xwidget)
{
  CHECK_XWIDGET (xwidget);
  GtkRequisition requisition;
  Lisp_Object rv;
  gtk_widget_size_request (XXWIDGET (xwidget)->widget_osr, &requisition);
  rv = Qnil;
  rv = Fcons (make_number (requisition.height), rv);
  rv = Fcons (make_number (requisition.width), rv);
  return rv;

}

DEFUN ("xwidgetp",
       Fxwidgetp, Sxwidgetp,
       1, 1, 0,
       doc: /* Return t if OBJECT is a xwidget.  */)
  (Lisp_Object object)
{
  return XWIDGETP (object) ? Qt : Qnil;
}

DEFUN ("xwidget-view-p",
       Fxwidget_view_p, Sxwidget_view_p,
       1, 1, 0,
       doc: /* Return t if OBJECT is a xwidget-view.  */)
  (Lisp_Object object)
{
  return XWIDGET_VIEW_P (object) ? Qt : Qnil;
}

DEFUN ("xwidget-info",
       Fxwidget_info, Sxwidget_info,
       1, 1, 0,
       doc: /* Return XWIDGET properties in a vector.  Currently [TYPE
TITLE WIDTH HEIGHT]. */)
  (Lisp_Object xwidget)
{
  CHECK_XWIDGET (xwidget);
  Lisp_Object info, n;
  struct xwidget *xw = XXWIDGET (xwidget);

  info = Fmake_vector (make_number (4), Qnil);
  ASET (info, 0, xw->type);
  ASET (info, 1, xw->title);
  XSETFASTINT (n, xw->width);
  ASET (info, 2, n);
  XSETFASTINT (n, xw->height);
  ASET (info, 3, n);

  return info;
}

DEFUN ("xwidget-view-info",
       Fxwidget_view_info, Sxwidget_view_info,
       1, 1, 0,
       doc: /* Return properties of XWIDGET-VIEW in a vector.
Currently [X Y CLIP_RIGHT CLIP_BOTTOM CLIP_TOP CLIP_LEFT] */)
  (Lisp_Object xwidget_view)
{
  CHECK_XWIDGET_VIEW (xwidget_view);
  struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
  Lisp_Object info;

  info = Fmake_vector (make_number (6), Qnil);
  ASET (info, 0, make_number (xv->x));
  ASET (info, 1, make_number (xv->y));
  ASET (info, 2, make_number (xv->clip_right));
  ASET (info, 3, make_number (xv->clip_bottom));
  ASET (info, 4, make_number (xv->clip_top));
  ASET (info, 5, make_number (xv->clip_left));

  return info;
}

DEFUN ("xwidget-view-model",
       Fxwidget_view_model, Sxwidget_view_model,
       1, 1, 0,
       doc:  /* Return the model associated with XWIDGET-VIEW. */)
  (Lisp_Object xwidget_view)
{
  CHECK_XWIDGET_VIEW (xwidget_view);
  return XXWIDGET_VIEW (xwidget_view)->model;
}

DEFUN ("xwidget-view-window",
       Fxwidget_view_window, Sxwidget_view_window,
       1, 1, 0,
       doc:  /* Return the window of XWIDGET-VIEW. */)
  (Lisp_Object xwidget_view)
{
  CHECK_XWIDGET_VIEW (xwidget_view);
  return XXWIDGET_VIEW (xwidget_view)->w;
}


DEFUN ("delete-xwidget-view",
       Fdelete_xwidget_view, Sdelete_xwidget_view,
       1, 1, 0,
       doc:  /* Delete the XWIDGET-VIEW. */)
  (Lisp_Object xwidget_view)
{
  CHECK_XWIDGET_VIEW (xwidget_view);
  struct xwidget_view *xv = XXWIDGET_VIEW (xwidget_view);
  gtk_widget_destroy (xv->widgetwindow);
  Vxwidget_view_list = Fdelq (xwidget_view, Vxwidget_view_list);
  // xv->model still has signals pointing to the view. There can be
  // several views. Find the matching signals and delete them all.
  g_signal_handlers_disconnect_matched  (XXWIDGET (xv->model)->widgetwindow_osr,
                                         G_SIGNAL_MATCH_DATA,
                                         0, 0, 0, 0,
                                         xv->widget);
  return Qnil;
}

DEFUN ("xwidget-view-lookup",
       Fxwidget_view_lookup, Sxwidget_view_lookup,
       1, 2, 0,
       doc: /* Return the xwidget-view associated with XWIDGET in
WINDOW if specified, otherwise it uses the selected window. Return nil
if no association is found.  */)
  (Lisp_Object xwidget, Lisp_Object window)
{
  CHECK_XWIDGET (xwidget);

  if (NILP (window))
    window = Fselected_window ();
  CHECK_WINDOW (window);

  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
       tail = XCDR (tail))
    {
      Lisp_Object xwidget_view = XCAR (tail);
      if (EQ (Fxwidget_view_model (xwidget_view), xwidget)
          && EQ (Fxwidget_view_window (xwidget_view), window))
        return xwidget_view;
    }

  return Qnil;
}

DEFUN ("xwidget-plist",
       Fxwidget_plist, Sxwidget_plist,
       1, 1, 0,
       doc: /* Return the plist of XWIDGET.  */)
  (register Lisp_Object xwidget)
{
  CHECK_XWIDGET (xwidget);
  return XXWIDGET (xwidget)->plist;
}

DEFUN ("xwidget-buffer",
       Fxwidget_buffer, Sxwidget_buffer,
       1, 1, 0,
       doc: /* Return the buffer of XWIDGET.  */)
  (register Lisp_Object xwidget)
{
  CHECK_XWIDGET (xwidget);
  return XXWIDGET (xwidget)->buffer;
}

DEFUN ("set-xwidget-plist",
       Fset_xwidget_plist, Sset_xwidget_plist,
       2, 2, 0,
       doc: /* Replace the plist of XWIDGET with PLIST.
Returns PLIST.  */)
  (register Lisp_Object xwidget, Lisp_Object plist)
{
  CHECK_XWIDGET (xwidget);
  CHECK_LIST (plist);

  XXWIDGET (xwidget)->plist = plist;
  return plist;
}

DEFUN ("set-xwidget-query-on-exit-flag",
       Fset_xwidget_query_on_exit_flag, Sset_xwidget_query_on_exit_flag,
       2, 2, 0,
       doc: /* Specify if query is needed for XWIDGET when
Emacs is exited.  If the second argument FLAG is non-nil, Emacs will
queries the user before exiting or killing a buffer if XWIDGET is
running.  This function returns FLAG. */)
  (Lisp_Object xwidget, Lisp_Object flag)
{
  CHECK_XWIDGET (xwidget);
  XXWIDGET (xwidget)->kill_without_query = NILP (flag);
  return flag;
}

DEFUN ("xwidget-query-on-exit-flag",
       Fxwidget_query_on_exit_flag, Sxwidget_query_on_exit_flag,
       1, 1, 0,
       doc: /* Return the current value of query-on-exit
flag for XWIDGET. */)
  (Lisp_Object xwidget)
{
  CHECK_XWIDGET (xwidget);
  return (XXWIDGET (xwidget)->kill_without_query ? Qnil : Qt);
}

void
syms_of_xwidget (void)
{

  defsubr (&Smake_xwidget);
  defsubr (&Sxwidgetp);
  DEFSYM (Qxwidgetp, "xwidgetp");
  defsubr (&Sxwidget_view_p);
  DEFSYM (Qxwidget_view_p, "xwidget-view-p");
  defsubr (&Sxwidget_info);
  defsubr (&Sxwidget_view_info);
  defsubr (&Sxwidget_resize);
  defsubr (&Sget_buffer_xwidgets);
  defsubr (&Sxwidget_view_model);
  defsubr (&Sxwidget_view_window);
  defsubr (&Sxwidget_view_lookup);
  defsubr (&Sxwidget_query_on_exit_flag);
  defsubr (&Sset_xwidget_query_on_exit_flag);

#ifdef HAVE_WEBKIT_OSR
  defsubr (&Sxwidget_webkit_goto_uri);
  defsubr (&Sxwidget_webkit_execute_script);
  defsubr (&Sxwidget_webkit_get_title);
  DEFSYM (Qwebkit_osr, "webkit-osr");
#endif

  defsubr (&Sxwidget_size_request);
  defsubr (&Sdelete_xwidget_view);

  defsubr (&Sxwidget_plist);
  defsubr (&Sxwidget_buffer);
  defsubr (&Sset_xwidget_plist);

  defsubr (&Sxwidget_set_adjustment);

  DEFSYM (Qxwidget, "xwidget");

  DEFSYM (QCxwidget, ":xwidget");
  DEFSYM (QCtitle, ":title");

  /* Do not forget to update the docstring of make-xwidget if you add
     new types. */

  DEFSYM (Qvertical, "vertical");
  DEFSYM (Qhorizontal, "horizontal");

  DEFSYM (QCplist, ":plist");

  DEFVAR_LISP ("xwidget-list", Vxwidget_list,
               doc:	/* xwidgets list.  */);
  Vxwidget_list = Qnil;

  DEFVAR_LISP ("xwidget-view-list", Vxwidget_view_list,
             doc:	/* xwidget views list.  */);
  Vxwidget_view_list = Qnil;

  Fprovide (intern ("xwidget-internal"), Qnil);

}


/* Value is non-zero if OBJECT is a valid Lisp xwidget specification.  A
   valid xwidget specification is a list whose car is the symbol
   `xwidget', and whose rest is a property list.  The property list must
   contain a value for key `:type'.  That value must be the name of a
   supported xwidget type.  The rest of the property list depends on the
   xwidget type.  */

bool
valid_xwidget_spec_p (Lisp_Object object)
{
  int valid_p = false;

  if (CONSP (object) && EQ (XCAR (object), Qxwidget))
      valid_p = true;

  return valid_p;
}



/* Find a value associated with key in spec.  */
Lisp_Object
xwidget_spec_value (Lisp_Object spec, Lisp_Object key, int *found)
{
  Lisp_Object tail;

  eassert (valid_xwidget_spec_p (spec));

  for (tail = XCDR (spec);
       CONSP (tail) && CONSP (XCDR (tail)); tail = XCDR (XCDR (tail)))
    {
      if (EQ (XCAR (tail), key))
        {
          if (found)
            *found = 1;
          return XCAR (XCDR (tail));
        }
    }

  if (found)
    *found = 0;
  return Qnil;
}


void
xwidget_view_delete_all_in_window (struct window *w)
{
  struct xwidget_view *xv = NULL;
  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
       tail = XCDR (tail))
    {
      if (XWIDGET_VIEW_P (XCAR (tail)))
        {
          xv = XXWIDGET_VIEW (XCAR (tail));
          if (XWINDOW (xv->w) == w)
            {
              Fdelete_xwidget_view (XCAR (tail));
            }
        }
    }
}

struct xwidget_view *
xwidget_view_lookup (struct xwidget *xw, struct window *w)
{
  Lisp_Object xwidget, window, ret;
  XSETXWIDGET (xwidget, xw);
  XSETWINDOW (window, w);

  ret = Fxwidget_view_lookup (xwidget, window);

  return EQ (ret, Qnil) ? NULL : XXWIDGET_VIEW (ret);
}

struct xwidget *
lookup_xwidget (Lisp_Object spec)
{
  /* When a xwidget lisp spec is found initialize the C struct that is
     used in the C code.  This is done by redisplay so values change
     if the spec changes.  So, take special care of one-shot events.
   */
  int found = 0;
  Lisp_Object value;
  struct xwidget *xw;

  value = xwidget_spec_value (spec, QCxwidget, &found);
  xw = XXWIDGET (value);

  return xw;
}

/* Set up detection of touched xwidget*/
void
xwidget_start_redisplay (void)
{
  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
       tail = XCDR (tail))
    {
      if (XWIDGET_VIEW_P (XCAR (tail)))
        XXWIDGET_VIEW (XCAR (tail))->redisplayed = 0;
    }
}

/* The xwidget was touched during redisplay, so it isn't a candidate
   for hiding.  */
void
xwidget_touch (struct xwidget_view *xv)
{
  xv->redisplayed = 1;
}

static int
xwidget_touched (struct xwidget_view *xv)
{
  return xv->redisplayed;
}

/* Redisplay has ended, now we should hide untouched xwidgets
*/
void
xwidget_end_redisplay (struct window *w, struct glyph_matrix *matrix)
{

  int i;
  int area;

  xwidget_start_redisplay ();
  // Iterate desired glyph matrix of window here, hide gtk widgets
  // not in the desired matrix.

  // This only takes care of xwidgets in active windows.  if a window
  // goes away from screen xwidget views wust be deleted

  //  dump_glyph_matrix (matrix, 2);
  for (i = 0; i < matrix->nrows; ++i)
    {
      //    dump_glyph_row (MATRIX_ROW (matrix, i), i, glyphs);
      struct glyph_row *row;
      row = MATRIX_ROW (matrix, i);
      if (row->enabled_p != 0)
        {
          for (area = LEFT_MARGIN_AREA; area < LAST_AREA; ++area)
            {
              struct glyph *glyph = row->glyphs[area];
              struct glyph *glyph_end = glyph + row->used[area];
              for (; glyph < glyph_end; ++glyph)
                {
                  if (glyph->type == XWIDGET_GLYPH)
                    {
                      /*
                        The only call to xwidget_end_redisplay is in dispnew
                         xwidget_end_redisplay (w->current_matrix);
                       */
                      xwidget_touch (xwidget_view_lookup (glyph->u.xwidget,
                                                          w));
                    }
                }
            }
        }
    }

  for (Lisp_Object tail = Vxwidget_view_list; CONSP (tail);
       tail = XCDR (tail))
    {
      if (XWIDGET_VIEW_P (XCAR (tail)))
        {
          struct xwidget_view *xv = XXWIDGET_VIEW (XCAR (tail));

          // "touched" is only meaningful for the current window, so
          // disregard other views.
          if (XWINDOW (xv->w) == w)
            {
              if (xwidget_touched (xv))
                xwidget_show_view (xv);
              else
                xwidget_hide_view (xv);
            }
        }
    }
}

/* Kill all xwidget in BUFFER. */
void
kill_buffer_xwidgets (Lisp_Object buffer)
{
  Lisp_Object tail, xwidget;
  for (tail = Fget_buffer_xwidgets (buffer); CONSP (tail); tail = XCDR (tail))
    {
      xwidget = XCAR (tail);
      Vxwidget_list = Fdelq (xwidget, Vxwidget_list);
      /* TODO free the GTK things in xw */
      {
        CHECK_XWIDGET (xwidget);
        struct xwidget *xw = XXWIDGET (xwidget);
        if (xw->widget_osr && xw->widgetwindow_osr)
          {
            gtk_widget_destroy (xw->widget_osr);
            gtk_widget_destroy (xw->widgetwindow_osr);
          }
      }
    }
}
